#ifndef TRACER_H_
#define TRACER_H_

#include <vector>
#include <limits>
#include "Ray.h"
#include "Camera.h"
#include "RenderTarget.h"
#include "Object.h"
#include "Light.h"
#include "Scene.h"

using namespace std;

class Tracer
{
public:
	Tracer(void){};
	~Tracer(void){};

	template <typename T>
	void Render(T & renderTarget, const Scene & scene, bool showProgress);

	Color TraceRay(const Ray & ray, const Scene & scene);

};


#define EPS 0.001f


template <typename T>
inline void FindClosestIntersection(const vector<T*> & objects, const Ray & ray, Object * &intersected, float * intersectionDepth)
{
	typename vector<T*>::const_iterator it;
	for(it = objects.begin() ; it != objects.end() ; it++)
	{
		float newDepth = (*it)->Intersect(ray);
		if (newDepth > EPS && newDepth < *intersectionDepth)
		{
			*intersectionDepth = newDepth;
			intersected = (*it);
		}
	}
}

template <typename T>
inline bool IntersectsAny(const vector<T*> & objects, const Ray & ray, float maxDepth)
{
	typename vector<T*>::const_iterator it;
	for(it = objects.begin() ; it != objects.end() ; it++)
	{
		//if ((*objIter) == intersected)
		//	continue;

		float t = (*it)->Intersect(ray);
		if (t > EPS && t < maxDepth)
		{
			return true;
		}
	}
	return false;
}

//float log2(float i)
//	{
//	return(log(i)/log(2));
//	}
float shift23=(1<<23);
float OOshift23=1.0/(1<<23);

float myLog2(float i)
	{
	float LogBodge=0.346607f;
	float x;
	float y;
	x=*(int *)&i;
	x*= OOshift23; //1/pow(2,23);
	x=x-127;

	y=x-floorf(x);
	y=(y-y*y)*LogBodge;
	return x+y;
	}
float myPow2(float i)
	{
	float PowBodge=0.33971f;
	float x;
	float y=i-floorf(i);
	y=(y-y*y)*PowBodge;

	x=i+127-y;
	x*= shift23; //pow(2,23);
	*(int*)&x=(int)x;
	return x;
	}

float maxA = 0.0f;

float powerFloatToPositiveInt(float a, float b)
	{
		if(a > maxA)
		{
			maxA = a;
		}
	return myPow2(b*myLog2(a));
	}

Color Tracer::TraceRay(const Ray & ray, const Scene & scene)
{
	Color color;
	float depth = numeric_limits<float>::infinity();
	Object * intersected = NULL;

	Scene::LightConstIterator lightIter;
	const Scene::LightList & lights = scene.GetLights();

	FindClosestIntersection(scene.GetSpheres(), ray, intersected, &depth);
	FindClosestIntersection(scene.GetPlanes(), ray, intersected, &depth);
	FindClosestIntersection(scene.GetMetaballGroups(), ray, intersected, &depth);

	if(intersected != NULL)
	{
		Vec3 intersectionPoint = ray._origin + ray._direction * depth;
		IntersectionInfo iInfo = intersected->GetIntersectionInfo(intersectionPoint);

		Vec3 normal = iInfo.Normal;
		normal.normalize();

		Material * material = iInfo.Mtrl;
		color = material->_ambient * scene.GetAmbientColor();

		for(lightIter = lights.begin() ; lightIter != lights.end() ; lightIter++)
		{
			Light * light = (*lightIter);

			Vec3 toLight = light->_position - intersectionPoint;
			float toLightLen = length(toLight);
			toLight.normalize();

			Ray pointToLightRay = Ray(intersectionPoint, toLight);

			if(IntersectsAny(scene.GetSpheres(), pointToLightRay, toLightLen)) continue;
			if(IntersectsAny(scene.GetPlanes(), pointToLightRay, toLightLen)) continue;
			if(IntersectsAny(scene.GetMetaballGroups(), pointToLightRay, toLightLen)) continue;

			float diffTerm = max(normal * toLight, 0.0f);

			Vec3 refl = toLight - 2*(toLight*normal)*normal;
			float base = -min(refl*(-ray._direction), 0.0f);

			float specTerm = 0.0f;
			if(base > 0.9f)
				specTerm = powerFloatToPositiveInt2(base, material->_specularPower);
			//if(specTerm > 0.5f)
			//{
			//	int t;
			//	t = 1;
			//}

			color += diffTerm * light->_diffuse * material->_diffuse;
			color += specTerm * light->_specular * material->_specular;
		}

		if(material->_transparency > 0.0f)
		{
			Ray rayFromIntersection = ray;
			rayFromIntersection._origin = intersectionPoint;
			color = (1 - material->_transparency) * color + material->_transparency * TraceRay(rayFromIntersection, scene);
		}

		if(material->_reflectance > 0.0f)
		{
			Vec3 srcDirInv = -ray._direction;
			Ray rayReflected;
			rayReflected._origin = intersectionPoint;
			rayReflected._direction = 2 * normal * (normal * srcDirInv) - srcDirInv;

			color = (1 - material->_reflectance) * color + material->_reflectance * TraceRay(rayReflected, scene);
		}
	}
	else
	{
		color = Color::BLACK;
	}

	return color;
}

template <typename T>
void Tracer::Render(T & renderTarget, const Scene & scene, bool showProgress)
{
	int w = renderTarget.GetWidth();
	int h = renderTarget.GetHeight();

	Scene::LightConstIterator lightIter;
	Scene::LightList lights = scene.GetLights();

	Color ambient = scene.GetAmbientColor();

	Vec3 currRayDir, rayDirAdvanceX, rayDirAdvanceY;
	scene.GetCamera().GetRayGenerationInfo(currRayDir, rayDirAdvanceX, rayDirAdvanceY);

	Vec3 cameraPos = scene.GetCamera().GetPosition();

	Vec3 currRowStartRayDir = currRayDir;

	for(int y=0 ; y<h ; y++)
	{
		for(int x=0 ; x<w ; x++)
		{
			Ray ray(cameraPos, normalize(currRayDir));

			Color color = TraceRay(ray, scene);


			color.normalizeAndScale(255.0f);

			RT_Pixel pixel;
			pixel.R = (unsigned char)color.R;
			pixel.G = (unsigned char)color.G;
			pixel.B = (unsigned char)color.B;
			renderTarget.SetPixel(x, y, pixel);

//			RGBApixel p;
//			p.Red = color.R;
//			p.Green = color.G;
//			p.Blue = color.B;
//			p.Alpha = color.A;
//			image.SetPixel(x, y, p);

			currRayDir += rayDirAdvanceX;
		}
		currRowStartRayDir += rayDirAdvanceY;
		currRayDir = currRowStartRayDir;

		if(showProgress == true)
			printf("\rRendered %d %%", int(100.0f*((float)y/(float)h)));
	}
}


#endif
